# -*- coding: utf-8 -*-

from hbcxx.cxx_format import *

from hbcxx.simcxx_types import *
from hbcxx.simcxx_vars import *
from hbcxx.simcxx_exp import simcxx_get_string
from hbcxx.simcxx_functions import *


def simcxx_convert_param(param_name: str, param_list: dict):
    if len(param_list) == 0:
        return ''
    cxx_param_def = ['typedef struct {}'.format(param_name),
                     '{']
    for param_var_name in param_list:
        param_var = param_list[param_var_name]
        if not isinstance(param_var, IVarParam):
            raise Exception('{} is not recognized as variable param.'.format(param_var))
        # Check the param variable.
        param_type = simcxx_get_type(param_var.type)
        cxx_param_def.append(CXX_INDENT + '{} {};'.format(param_type, param_var_name))
    cxx_param_def.append('} ' + '{};'.format(param_name))
    return '\n'.join(cxx_param_def)


def simcxx_convert_const(const_list: dict):
    cxx_const_def = []
    for const_name in const_list:
        const_var = const_list[const_name]
        if not isinstance(const_var, IVarConst):
            raise Exception('{} is not recognized as variable const.'.format(const_var))
        # Check the const variable.
        const_type = simcxx_get_type(const_var.type)
        const_def_name = simcxx_get_cxx_const(const_name)
        # Construct the variable.
        cxx_const_def.append('{} {};'.format(const_type, const_def_name))
    return cxx_const_def


def simcxx_convert_reg(reg_list: dict):
    cxx_reg_def = []
    for reg_name in reg_list:
        reg_var = reg_list[reg_name]
        if not isinstance(reg_var, IVarReg):
            raise Exception('{} is not recognized as variable reg.'.format(reg_var))
        # Check the register variable.
        reg_type = simcxx_get_type(reg_var.type)
        # Create the definition code.
        reg_def_name = simcxx_get_cxx_reg(reg_name)
        cxx_reg_def.append('{} {};'.format(reg_type, reg_def_name))
    return cxx_reg_def


def simccx_convert_port(port_list: dict):
    cxx_port_def = []
    for port_name in port_list:
        port_var = port_list[port_name]
        if not isinstance(port_var, IVarPort):
            raise Exception('{} is not recognized as variable port.'.format(port_var))
        # Check the port variable.
        port_type = simcxx_get_type(port_var.type)
        # Create the definition code.
        port_def_name = simcxx_get_cxx_port(port_name)
        cxx_port_def.append('{} {};'.format(port_type, port_def_name))
    return cxx_port_def


def simcxx_convert_node_vars(node_list: dict):
    cxx_node_def = []
    for node_name in node_list:
        node_var = node_list[node_name]
        if not isinstance(node_var, IVarNode):
            raise Exception('{} is not recognized as variable node.'.format(node_var))
        # Check the node variable.
        node_type = simcxx_get_type(node_var.type)
        # Create the definition code.
        node_def_name = simcxx_get_cxx_node(node_name)
        cxx_node_def.append('{} {};'.format(node_type, node_def_name))
    return cxx_node_def


def simcxx_convert_task_list(node_name: str, task_list: IExpValue):
    # Create the cxx codes.
    cxx_construct_list = []
    if task_list is not None:
        # The value should packed as a string list.
        ir_task_list = task_list.value.value
        for task_name in ir_task_list:
            cxx_construct_list.append(
                CXX_INDENT + 'list.append(static_cast<NodeTask>(&{}::{}));'.format(node_name, task_name))
    # Create the cpp function.
    cxx_codes = ['NodeTaskList {}::tasks() const'.format(node_name),
                 '{',
                 CXX_INDENT + 'NodeTaskList list;',
                 *cxx_construct_list,
                 CXX_INDENT + 'return list;',
                  '}']
    return '\n'.join(cxx_codes)


def simcxx_convert_node(node_type: ITypeNode):
    node_name = node_type.name
    # Create the parameter struct.
    param_type_name = simcxx_get_cxx_param(node_name)
    param_def = \
        simcxx_convert_param(param_type_name, node_type.params)
    # Prepare the member list
    node_member_vars = []
    # Create the definition of the variables.
    node_member_vars += simcxx_convert_const(node_type.consts) # consts
    node_member_vars += simcxx_convert_reg(node_type.regs) # regs
    node_member_vars += simccx_convert_port(node_type.ports) # ports
    node_member_vars += simcxx_convert_node_vars(node_type.nodes) # nodes
    # Construct the header file.
    initial_param = 'Node *parent'
    if len(param_def) > 0:
        initial_param += ', {} params'.format(param_type_name)
        h_codes = [param_def]
    else:
        h_codes = []
    node_task_def = ['']
    if node_type.tasks is not None:
        task_list = node_type.tasks.value.value
        if len(task_list) > 0:
            for task_func_name in task_list:
                node_task_def.append(CXX_INDENT + 'void {}();'.format(
                    simcxx_get_string(task_func_name.value)))
    if len(node_task_def) == 1:
        node_task_def = []
    node_header = ['class {} : public Node'.format(node_name),
                   '{',
                   'public:',
                   '\n'.join(CXX_INDENT + var_def for var_def in node_member_vars),
                   '',
                   CXX_INDENT + '{}({});'.format(node_name, initial_param),
                   CXX_INDENT + 'NodeTaskList tasks() const override;',
                   *node_task_def,
                   '};']
    # Construct the node header.
    h_codes.append('\n'.join(node_header))
    # Construct the node cpp file.
    #  -- Constructor & task list--
    cpp_codes = [simcxx_convert_func_initial(node_name, initial_param,
                                             node_type.initial),
                 simcxx_convert_task_list(node_name, node_type.tasks)]
    #  -- Tasks --
    for ir_task in node_type.task_body:
        cpp_codes.append(simcxx_convert_func_task(node_name, ir_task))
    return h_codes, cpp_codes


def simcxx_convert_nodes(node_types: list):
    h_codes = []
    cpp_codes = []
    # Loop and convert all the nodes.
    for node_type in node_types:
        node_h_code, node_cpp_codes = simcxx_convert_node(node_type)
        h_codes += node_h_code
        cpp_codes += node_cpp_codes
    if len(h_codes) > 0:
        simcxx_add_include('"node.h"')
    return h_codes, cpp_codes